﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using SilkroadSecurityApi;
using System.Threading;
using System.IO;
using System.Xml;
using System.Windows.Forms;

namespace SimpleProxy
{
    class Program
    {
        static TcpListener gw_local_server;
        static TcpClient gw_local_client;
        static TcpClient gw_remote_client;
        static Security gw_local_security;
        static Security gw_remote_security;
        static NetworkStream gw_local_stream;
        static NetworkStream gw_remote_stream;
        static TransferBuffer gw_remote_recv_buffer;
        static List<Packet> gw_remote_recv_packets;
        static List<KeyValuePair<TransferBuffer, Packet>> gw_remote_send_buffers;
        static TransferBuffer gw_local_recv_buffer;
        static List<Packet> gw_local_recv_packets;
        static List<KeyValuePair<TransferBuffer, Packet>> gw_local_send_buffers;
        
        static TcpListener ag_local_server;
        static TcpClient ag_local_client;
        static TcpClient ag_remote_client;
        static Security ag_local_security;
        static Security ag_remote_security;
        static NetworkStream ag_local_stream;
        static NetworkStream ag_remote_stream;
        static TransferBuffer ag_remote_recv_buffer;
        static List<Packet> ag_remote_recv_packets;
        static List<KeyValuePair<TransferBuffer, Packet>> ag_remote_send_buffers;
        static TransferBuffer ag_local_recv_buffer;
        static List<Packet> ag_local_recv_packets;
        static List<KeyValuePair<TransferBuffer, Packet>> ag_local_send_buffers;

        static string xfer_remote_ip;
        static int xfer_remote_port;

        static object exit_lock = new object();
        static bool should_exit = false;
        static XmlWriter xml_writer;
        static int xml_index = 0;

        static void WritePacket(Packet packet, byte[] packet_bytes, string direction)
        {
            lock (xml_writer)
            {
                xml_writer.WriteStartElement("packet");
                xml_writer.WriteAttributeString("Index", xml_index.ToString());
                xml_index++;
                xml_writer.WriteAttributeString("Time", DateTime.Now.Ticks.ToString());
                xml_writer.WriteAttributeString("Direction", direction);
                xml_writer.WriteAttributeString("Opcode", packet.Opcode.ToString());
                xml_writer.WriteAttributeString("Encrypted", packet.Encrypted.ToString());
                xml_writer.WriteAttributeString("Massive", packet.Massive.ToString());
                xml_writer.WriteAttributeString("Length", packet_bytes.Length.ToString());
                xml_writer.WriteAttributeString("Payload", Convert.ToBase64String(packet_bytes, 0, packet_bytes.Length));
                xml_writer.WriteEndElement();
                xml_writer.Flush();
            }
        }

        static void GatewayRemoteThread()
        {
            try
            {
                while (true)
                {
                    lock (exit_lock)
                    {
                        if (should_exit)
                        {
                            break;
                        }
                    }

                    if(gw_remote_stream.DataAvailable)
                    {
                        gw_remote_recv_buffer.Offset = 0;
                        gw_remote_recv_buffer.Size = gw_remote_stream.Read(gw_remote_recv_buffer.Buffer, 0, gw_remote_recv_buffer.Buffer.Length);
                        gw_remote_security.Recv(gw_remote_recv_buffer);
                    }

                    gw_remote_recv_packets = gw_remote_security.TransferIncoming();
                    if (gw_remote_recv_packets != null)
                    {
                        foreach (Packet packet in gw_remote_recv_packets)
                        {
                            byte[] packet_bytes = packet.GetBytes();
                            Console.WriteLine("[S->P][{0:X4}][{1} bytes]{2}{3}{4}{5}{6}", packet.Opcode, packet_bytes.Length, packet.Encrypted ? "[Encrypted]" : "", packet.Massive ? "[Massive]" : "", Environment.NewLine, Utility.HexDump(packet_bytes), Environment.NewLine);

                            WritePacket(packet, packet_bytes, "S->C");

                            // Do not pass through these packets.
                            if (packet.Opcode == 0x5000 || packet.Opcode == 0x9000)
                            {
                                continue;
                            }

                            if (packet.Opcode == 0xA102)
                            {
                                byte result = packet.ReadUInt8();
                                if (result == 1)
                                {
                                    uint id = packet.ReadUInt32();
                                    string ip = packet.ReadAscii();
                                    ushort port = packet.ReadUInt16();

                                    xfer_remote_ip = ip;
                                    xfer_remote_port = port;

                                    Packet new_packet = new Packet(0xA102, true);
                                    new_packet.WriteUInt8(result);
                                    new_packet.WriteUInt32(id);
                                    new_packet.WriteAscii("127.0.0.1");
                                    new_packet.WriteUInt16(20001);
                                    
                                    gw_local_security.Send(new_packet);

                                    continue;
                                }
                            }

                            gw_local_security.Send(packet);
                        }
                    }

                    gw_remote_send_buffers = gw_remote_security.TransferOutgoing();
                    if (gw_remote_send_buffers != null)
                    {
                        foreach (var kvp in gw_remote_send_buffers)
                        {
                            Packet packet = kvp.Value;
                            TransferBuffer buffer = kvp.Key;

                            byte[] packet_bytes = packet.GetBytes();
                            //Console.WriteLine("[P->S][{0:X4}][{1} bytes]{2}{3}{4}{5}{6}", packet.Opcode, packet_bytes.Length, packet.Encrypted ? "[Encrypted]" : "", packet.Massive ? "[Massive]" : "", Environment.NewLine, Utility.HexDump(packet_bytes), Environment.NewLine);

                            WritePacket(packet, packet_bytes, "C->S");

                            gw_remote_stream.Write(buffer.Buffer, 0, buffer.Size);
                        }
                    }

                    Thread.Sleep(1);
                }
            }
            catch (System.Exception ex)
            {
                Console.WriteLine("[GatewayRemoteThread] Exception: {0}", ex);
            }
        }

        static void GatewayLocalThread()
        {
            try
            {
                while (true)
                {
                    lock (exit_lock)
                    {
                        if (should_exit)
                        {
                            break;
                        }
                    }

                    if (gw_local_stream.DataAvailable)
                    {
                        gw_local_recv_buffer.Offset = 0;
                        gw_local_recv_buffer.Size = gw_local_stream.Read(gw_local_recv_buffer.Buffer, 0, gw_local_recv_buffer.Buffer.Length);
                        gw_local_security.Recv(gw_local_recv_buffer);
                    }

                    gw_local_recv_packets = gw_local_security.TransferIncoming();
                    if (gw_local_recv_packets != null)
                    {
                        foreach (Packet packet in gw_local_recv_packets)
                        {
                            byte[] packet_bytes = packet.GetBytes();
                            Console.WriteLine("[C->P][{0:X4}][{1} bytes]{2}{3}{4}{5}{6}", packet.Opcode, packet_bytes.Length, packet.Encrypted ? "[Encrypted]" : "", packet.Massive ? "[Massive]" : "", Environment.NewLine, Utility.HexDump(packet_bytes), Environment.NewLine);

                            // Do not pass through these packets.
                            if (packet.Opcode == 0x5000 || packet.Opcode == 0x9000 || packet.Opcode == 0x2001)
                            {
                                continue;
                            }

                            gw_remote_security.Send(packet);
                        }
                    }

                    gw_local_send_buffers = gw_local_security.TransferOutgoing();
                    if (gw_local_send_buffers != null)
                    {
                        foreach (var kvp in gw_local_send_buffers)
                        {
                            Packet packet = kvp.Value;
                            TransferBuffer buffer = kvp.Key;

                            byte[] packet_bytes = packet.GetBytes();
                            //Console.WriteLine("[P->C][{0:X4}][{1} bytes]{2}{3}{4}{5}{6}", packet.Opcode, packet_bytes.Length, packet.Encrypted ? "[Encrypted]" : "", packet.Massive ? "[Massive]" : "", Environment.NewLine, Utility.HexDump(packet_bytes), Environment.NewLine);

                            gw_local_stream.Write(buffer.Buffer, 0, buffer.Size);
                        }
                    }

                    Thread.Sleep(1);
                }
            }
            catch (System.Exception ex)
            {
                Console.WriteLine("[GatewayLocalThread] Exception: {0}", ex);
            }
        }

        static void GatewayThread()
        {
            try
            {
                gw_local_security = new Security();
                gw_local_security.GenerateSecurity(true, true, true);

                gw_remote_security = new Security();

                gw_remote_recv_buffer = new TransferBuffer(4096, 0, 0);
                gw_local_recv_buffer = new TransferBuffer(4096, 0, 0);

                gw_local_server = new TcpListener(IPAddress.Parse("127.0.0.1"), 20000);
                gw_local_server.Start();

                Console.WriteLine("Waiting for a connection... ");
                
                gw_local_client = gw_local_server.AcceptTcpClient();
                gw_remote_client = new TcpClient();

                Console.WriteLine("A connection has been made!");

                gw_local_server.Stop();

                Console.WriteLine("Connecting to Joymax... ");
                
                gw_remote_client.Connect("gwgt1.joymax.com", 15779);

                Console.WriteLine("The connection has been made!");

                gw_local_stream = gw_local_client.GetStream();
                gw_remote_stream = gw_remote_client.GetStream();

                Thread remote_thread = new Thread(GatewayRemoteThread);
                remote_thread.Start();

                Thread local_thread = new Thread(GatewayLocalThread);
                local_thread.Start();

                remote_thread.Join();
                local_thread.Join();
            }
            catch (Exception ex)
            {
                Console.WriteLine("[GatewayThread] Exception: {0}", ex);
            }
        }

        static void AgentRemoteThread()
        {
            try
            {
                while (true)
                {
                    lock (exit_lock)
                    {
                        if (should_exit)
                        {
                            break;
                        }
                    }

                    if (ag_remote_stream.DataAvailable)
                    {
                        ag_remote_recv_buffer.Offset = 0;
                        ag_remote_recv_buffer.Size = ag_remote_stream.Read(ag_remote_recv_buffer.Buffer, 0, ag_remote_recv_buffer.Buffer.Length);
                        ag_remote_security.Recv(ag_remote_recv_buffer);
                    }

                    ag_remote_recv_packets = ag_remote_security.TransferIncoming();
                    if (ag_remote_recv_packets != null)
                    {
                        foreach (Packet packet in ag_remote_recv_packets)
                        {
                            byte[] packet_bytes = packet.GetBytes();
                            Console.WriteLine("[S->P][{0:X4}][{1} bytes]{2}{3}{4}{5}{6}", packet.Opcode, packet_bytes.Length, packet.Encrypted ? "[Encrypted]" : "", packet.Massive ? "[Massive]" : "", Environment.NewLine, Utility.HexDump(packet_bytes), Environment.NewLine);

                            WritePacket(packet, packet_bytes, "S->C");

                            // Do not pass through these packets.
                            if (packet.Opcode == 0x5000 || packet.Opcode == 0x9000)
                            {
                                continue;
                            }

                            ag_local_security.Send(packet);
                        }
                    }

                    ag_remote_send_buffers = ag_remote_security.TransferOutgoing();
                    if (ag_remote_send_buffers != null)
                    {
                        foreach (var kvp in ag_remote_send_buffers)
                        {
                            Packet packet = kvp.Value;
                            TransferBuffer buffer = kvp.Key;

                            byte[] packet_bytes = packet.GetBytes();
                            Console.WriteLine("[P->S][{0:X4}][{1} bytes]{2}{3}{4}{5}{6}", packet.Opcode, packet_bytes.Length, packet.Encrypted ? "[Encrypted]" : "", packet.Massive ? "[Massive]" : "", Environment.NewLine, Utility.HexDump(packet_bytes), Environment.NewLine);

                            WritePacket(packet, packet_bytes, "C->S");

                            ag_remote_stream.Write(buffer.Buffer, 0, buffer.Size);
                        }
                    }

                    Thread.Sleep(1);
                }
            }
            catch (System.Exception ex)
            {
                Console.WriteLine("[AgentRemoteThread] Exception: {0}", ex);
            }
        }

        static void AgentLocalThread()
        {
            try
            {
                while (true)
                {
                    lock (exit_lock)
                    {
                        if (should_exit)
                        {
                            break;
                        }
                    }

                    if (ag_local_stream.DataAvailable)
                    {
                        ag_local_recv_buffer.Offset = 0;
                        ag_local_recv_buffer.Size = ag_local_stream.Read(ag_local_recv_buffer.Buffer, 0, ag_local_recv_buffer.Buffer.Length);
                        ag_local_security.Recv(ag_local_recv_buffer);
                    }

                    ag_local_recv_packets = ag_local_security.TransferIncoming();
                    if (ag_local_recv_packets != null)
                    {
                        foreach (Packet packet in ag_local_recv_packets)
                        {
                            byte[] packet_bytes = packet.GetBytes();
                            Console.WriteLine("[C->P][{0:X4}][{1} bytes]{2}{3}{4}{5}{6}", packet.Opcode, packet_bytes.Length, packet.Encrypted ? "[Encrypted]" : "", packet.Massive ? "[Massive]" : "", Environment.NewLine, Utility.HexDump(packet_bytes), Environment.NewLine);

                            // Do not pass through these packets.
                            if (packet.Opcode == 0x5000 || packet.Opcode == 0x9000 || packet.Opcode == 0x2001)
                            {
                                continue;
                            }

                            ag_remote_security.Send(packet);
                        }
                    }

                    ag_local_send_buffers = ag_local_security.TransferOutgoing();
                    if (ag_local_send_buffers != null)
                    {
                        foreach (var kvp in ag_local_send_buffers)
                        {
                            Packet packet = kvp.Value;
                            TransferBuffer buffer = kvp.Key;

                            byte[] packet_bytes = packet.GetBytes();
                            //Console.WriteLine("[P->C][{0:X4}][{1} bytes]{2}{3}{4}{5}{6}", packet.Opcode, packet_bytes.Length, packet.Encrypted ? "[Encrypted]" : "", packet.Massive ? "[Massive]" : "", Environment.NewLine, Utility.HexDump(packet_bytes), Environment.NewLine);

                            ag_local_stream.Write(buffer.Buffer, 0, buffer.Size);
                        }
                    }

                    Thread.Sleep(1);
                }
            }
            catch (System.Exception ex)
            {
                Console.WriteLine("[AgentLocalThread] Exception: {0}", ex);
            }
        }

        static void AgentThread()
        {
            try
            {
                ag_local_security = new Security();
                ag_local_security.GenerateSecurity(true, true, true);

                ag_remote_security = new Security();

                ag_remote_recv_buffer = new TransferBuffer(4096, 0, 0);
                ag_local_recv_buffer = new TransferBuffer(4096, 0, 0);

                ag_local_server = new TcpListener(IPAddress.Parse("127.0.0.1"), 20001);
                ag_local_server.Start();

                Console.WriteLine("Waiting for a connection... ");

                ag_local_client = ag_local_server.AcceptTcpClient();
                ag_remote_client = new TcpClient();

                Console.WriteLine("A connection has been made!");

                ag_local_server.Stop();

                Console.WriteLine("Connecting to {0}:{1}", xfer_remote_ip, xfer_remote_port);

                ag_remote_client.Connect(xfer_remote_ip, xfer_remote_port);

                Console.WriteLine("The connection has been made!");

                ag_local_stream = ag_local_client.GetStream();
                ag_remote_stream = ag_remote_client.GetStream();

                Thread remote_thread = new Thread(AgentRemoteThread);
                remote_thread.Start();

                Thread local_thread = new Thread(AgentLocalThread);
                local_thread.Start();

                remote_thread.Join();
                local_thread.Join();
            }
            catch (Exception ex)
            {
                Console.WriteLine("[AgentThread] Exception: {0}", ex);
            }
        }

        static void ExitThread()
        {
            MessageBox.Show("Press OK to exit...");
            lock (exit_lock)
            {
                should_exit = true;
                ag_local_server.Stop();
                gw_local_server.Stop();
            }
        }

        static void Main(string[] args)
        {
            TextWriter text_writer = new StreamWriter(String.Format("log_{0}.xml", DateTime.Now.Ticks));
            xml_writer = XmlWriter.Create(text_writer);

            xml_writer.WriteStartElement("packets");

            try
            {
                Thread gw = new Thread(GatewayThread);
                gw.Start();

                Thread ag = new Thread(AgentThread);
                ag.Start();

                Thread e = new Thread(ExitThread);
                e.Start();

                e.Join();
                gw.Join();
                ag.Join();
            }
            catch (System.Exception ex)
            {
                Console.WriteLine("[Main] Exception: {0}", ex);
            }

            xml_writer.WriteEndElement();

            xml_writer.Close();
        }
    }
}
